/*
 * Copyright (C) 2012 CNL - ECE Dept - National University of Singapore
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA.
 */
package sg.nus.ece.cnl.veracity.common;

import com.xensource.xenapi.*;
import org.apache.log4j.Logger;

/**
 * Creates a VM on the default SR with a network and DVD drive.
 *
 * @author tram
 */
public class VMCreator {

    private static final Logger log = Logger.getLogger(VMCreator.class);
    // vm creator instance for this session
    private static VMCreator vmCreatorInstance;
    // session associated with this creator
    private SessionBase session;

    private VMCreator(SessionBase _session) {

        session = _session;
    }

    public static VMCreator getInstance(SessionBase _session) {

        if (vmCreatorInstance == null) {
            vmCreatorInstance = new VMCreator(_session);
        }

        return vmCreatorInstance;
    }

    /**
     * Create and start a new virtual machine (VM) given the name and its
     * template
     *
     * @param newVmName name of the new VM
     * @param templateNameLabel VM template of this VM
     * @return a new vm successfully started
     * @throws VeracityException
     */
    public VM createVM(String newVmName, String templateNameLabel) 
            throws sg.nus.ece.cnl.veracity.exception.VeracityException {

        VM newVm = null;
        try {
            // get the VM template associated with a templateNameLabel
            log.info("Getting the VM template associated with the name \"" + templateNameLabel + "\" ...");
            VM template = session.getVMTemplate(templateNameLabel);
            if (template == null) {
                throw new sg.nus.ece.cnl.veracity.exception.VeracityException("VM template \"" + templateNameLabel + "\" does not exist.");
            }

            // clone the template
            log.info("Cloning the VM template ...");
            newVm = template.createClone(session.getConnection(), newVmName);

            // if new vm does not have a virtual network interface, create one for it
            if (newVm.getVIFs(session.getConnection()) == null) {
                // find a network
                log.info("Find a network ...");
                Network network = session.getAllNetworks().get(0);
                if (network == null) {
                    throw new sg.nus.ece.cnl.veracity.exception.VeracityException("Network not found.");
                }

                // attach the clone and network to each other with a VIF
                if (makeVIF(newVm, network, "0") == null) {
                    throw new sg.nus.ece.cnl.veracity.exception.VeracityException("Cannot attach the clone vm and its network");
                }
            }

            // get storage repository of the new VM
//            Map<String, String> otherConfig = newVm.getOtherConfig(this.session.getConnection());
//            String disks = otherConfig.get("disks");
//            if (disks == null || disks.isEmpty()) {
//                // find a storage repository
//                log.info("Find a storage repository ...");
//                SR defaultSR = this.session.getDefaultStorageRepository();
//                disks = "sr=\"" + defaultSR.getUuid(this.session.getConnection()) + "\"";
//                // put the Storage Repository uuid into the provision XML
//                otherConfig.put("disks", disks);
//                newVm.setOtherConfig(this.session.getConnection(), otherConfig);
//            }

            if (newVm.getVBDs(session.getConnection()) == null) {
                if (makeCDDrive(newVm) == null) {
                    throw new sg.nus.ece.cnl.veracity.exception.VeracityException("Cannot create a new VBD instance for the new VM");
                }
            }
            // provision the disks
            newVm.provision(session.getConnection());
            
            log.info("Starting new VM.....");
            newVm.start(session.getConnection(), false, false);

        } catch (com.xensource.xenapi.Types.XenAPIException ex) {
            // exception throwed by the XEN API
            throw new sg.nus.ece.cnl.veracity.exception.VeracityException(ex);
        } catch (org.apache.xmlrpc.XmlRpcException ex) {
            // exception throwed by the XmlRpc
            throw new sg.nus.ece.cnl.veracity.exception.VeracityException(ex);
        }

        return newVm;
    }

    /**
     * Create a VIF by making a VIF.record and then filling in the necessary
     * fields
     *
     * @param newVm new VM
     * @param network network of the new VM
     * @param device device of the new VM
     * @return VIF instance
     * @throws VeracityException
     */
    private VIF makeVIF(VM newVm, Network network, String device) 
            throws sg.nus.ece.cnl.veracity.exception.VeracityException {

        VIF newVIF = null;
        try {
            VIF.Record newvifrecord = new VIF.Record();
            // These three parameters are used in the command line VIF creation
            newvifrecord.VM = newVm;
            newvifrecord.network = network;
            newvifrecord.device = device;
            newvifrecord.MTU = 1500L;

            newVIF = VIF.create(session.getConnection(), newvifrecord);

        } catch (com.xensource.xenapi.Types.XenAPIException ex) {
            // exception throwed by the XEN API
            throw new sg.nus.ece.cnl.veracity.exception.VeracityException(ex);
        } catch (org.apache.xmlrpc.XmlRpcException ex) {
            // exception throwed by the XmlRpc
            throw new sg.nus.ece.cnl.veracity.exception.VeracityException(ex);
        }

        return newVIF;
    }

    /**
     * Create a new VBD instance for the new VM
     *
     * @param vm new VM instance
     * @return VBD instance
     * @throws VeracityException
     */
    private VBD makeCDDrive(VM vm) 
            throws sg.nus.ece.cnl.veracity.exception.VeracityException {

        VBD newVBD = null;
        try {
            VBD.Record vbdrecord = new VBD.Record();

            vbdrecord.VM = vm;
            vbdrecord.VDI = null;
            vbdrecord.userdevice = "3";
            vbdrecord.mode = Types.VbdMode.RO;
            vbdrecord.type = Types.VbdType.CD;
            vbdrecord.empty = true;

            newVBD = VBD.create(session.getConnection(), vbdrecord);
        } catch (com.xensource.xenapi.Types.XenAPIException ex) {
            // exception throwed by the XEN API
            throw new sg.nus.ece.cnl.veracity.exception.VeracityException(ex);
        } catch (org.apache.xmlrpc.XmlRpcException ex) {
            // exception throwed by the XmlRpc
            throw new sg.nus.ece.cnl.veracity.exception.VeracityException(ex);
        }
        return newVBD;
    }
}
